#include "EpilInterpreter.h"
#include "EpilScanner.h"
#include "EpilParser.h"
#include "EpilStringObject.h"
#include "EpilPointerHelper.h"
#include "EpilChunkInterpreter.h"
#include "EpilInterpreterException.h"
#include "EpilBuiltInFunctions.h"
#include "EpilFunctionScope.h"
#include <iomanip>

using namespace std;


EpilInterpreter::EpilInterpreter()
{
	m_nilManager = new EpilNilManager( this );
	m_booleanManager = new EpilBooleanManager( this );
	m_numberManager = new EpilNumberManager( this );

	m_globalScope = new EpilScope( this, NULL );
	m_curScope = m_globalScope;

	EpilBuiltInFunctions::RegisterFunctions( this );
	this->SetGlobalVariable( t("_G"), m_globalScope->GetLocals().CreateCopy() );
}


EpilInterpreter::~EpilInterpreter()
{
	for( map<tstring, EpilChunkCode*>::iterator it = m_fileChunks.begin(); it != m_fileChunks.end(); ++it )
	{
		EpilPointerHelper::SafeDelete( it->second );
	}

	while( m_curScope )
	{
		EpilScope * parent = m_curScope->GetParent();
		EpilPointerHelper::SafeDelete( m_curScope );
		m_curScope = parent;
	}

	EpilPointerHelper::SafeDelete( m_nilManager );
	EpilPointerHelper::SafeDelete( m_booleanManager );
	EpilPointerHelper::SafeDelete( m_numberManager );
}


void EpilInterpreter::ReleaseObject( EpilObject * obj )
{
	switch( obj->GetType() )
	{
	case EOT_Boolean:
		this->ReleaseBooleanObject( static_cast<EpilBooleanObject*>( obj ) );
		break;
	case EOT_Number:
		this->ReleaseNumberObject( static_cast<EpilNumberObject*>( obj ) );
		break;
	case EOT_String:
		this->ReleaseStringObject( static_cast<EpilStringObject*>( obj ) );
		break;
	case EOT_Nil:
		this->ReleaseNilObject( static_cast<EpilNilObject*>( obj ) );
		break;
	case EOT_Table:
		this->ReleaseTableObject( static_cast<EpilTableObject*>( obj ) );
		break;
	case EOT_Function:
		{
			EpilFunctionObject * funcObj = static_cast<EpilFunctionObject*>( obj );
			switch( funcObj->GetFunctionType() )
			{
			case EFT_Native:
				this->ReleaseNativeFunctionObject( static_cast<EpilNativeFunctionObject*>( funcObj ) );
				break;
			case EFT_Script:
				this->ReleaseScriptFunctionObject( static_cast<EpilScriptFunctionObject*>( funcObj ) );
				break;
			}
		}
		break;
	}
}


EpilBooleanObject * EpilInterpreter::CreateBooleanObject( bool value )
{
	return m_booleanManager->CreateBooleanObject( value );
}


void EpilInterpreter::ReleaseBooleanObject( EpilBooleanObject * obj )
{
	m_booleanManager->ReleaseBooleanObject( obj );
}


EpilNumberObject * EpilInterpreter::CreateNumberObject( double value )
{
	return m_numberManager->CreateNumberObject( value );
}


void EpilInterpreter::ReleaseNumberObject( EpilNumberObject * obj )
{
	m_numberManager->ReleaseNumberObject( obj );
}


EpilStringObject * EpilInterpreter::CreateStringObject( const tstring & value )
{
	EpilStringObject * obj = new EpilStringObject( this, value );
	return obj;
}


void EpilInterpreter::ReleaseStringObject( EpilStringObject * obj )
{
	EpilPointerHelper::SafeDelete( obj );
}


EpilNilObject * EpilInterpreter::CreateNilObject()
{
	return m_nilManager->CreateNilObject();
}


void EpilInterpreter::ReleaseNilObject( EpilNilObject * obj )
{
	m_nilManager->ReleaseNilObject( obj );
}


EpilTableObject * EpilInterpreter::CreateTableObject()
{
	return new EpilTableObject( this );
}


void EpilInterpreter::ReleaseTableObject( EpilTableObject * obj )
{
	EpilPointerHelper::SafeDelete( obj );
}


EpilNativeFunctionObject * EpilInterpreter::CreateNativeFunctionObject( EpilNativeFunction function )
{
	EpilNativeFunctionObject * obj = new EpilNativeFunctionObject( this );
	obj->SetFunction( function );
	return obj;
}


void EpilInterpreter::ReleaseNativeFunctionObject( EpilNativeFunctionObject * obj )
{
	EpilPointerHelper::SafeDelete( obj );
}


EpilScriptFunctionObject * EpilInterpreter::CreateScriptFunctionObject( const list<tstring> & parameters, const EpilChunkCode * block, bool hasVariousParameters )
{
	EpilScriptFunctionObject * obj = new EpilScriptFunctionObject( this, parameters, block, hasVariousParameters );
	return obj;
}


void EpilInterpreter::ReleaseScriptFunctionObject( EpilScriptFunctionObject * obj )
{
	EpilPointerHelper::SafeDelete( obj );
}


void EpilInterpreter::LoadFile( const tstring & filename )
{
	if( m_fileChunks.find( filename ) != m_fileChunks.end() )
	{
		return;
	}

	EpilScanner scanner;
	scanner.ScanFile( filename );
	const list<EpilToken> & tokens = scanner.GetTokens();

	EpilParser parser( tokens );
	parser.Parse();

	m_fileChunks.insert( make_pair( filename, parser.GetChunk() ) );
}


void EpilInterpreter::RunFile( const tstring & filename )
{
	this->LoadFile( filename );
	EpilChunkCode * chunk = m_fileChunks[filename];

	this->EnterFunctionScope( true );

	EpilChunkInterpreter ci( this );
	try
	{
		ci.Run( chunk );
	}
	catch( ... )
	{
		this->LeaveScope();
		throw;
	}

	EpilChunkExitReason reason = ci.GetExitReason();
	if( reason == ECER_Break )
	{
		this->LeaveScope();
		throw EpilInterpreterException( t("No loop to break."), ci.GetExitLocation() );
	}
	if( reason == ECER_Continue )
	{
		this->LeaveScope();
		throw EpilInterpreterException( t("No loop to continue."), ci.GetExitLocation() );
	}

	this->LeaveScope();
}


void EpilInterpreter::RunString( const tstring & code )
{
	EpilScanner scanner;
	scanner.ScanString( code );
	const list<EpilToken> & tokens = scanner.GetTokens();

	EpilParser parser( tokens );
	parser.Parse();
	EpilChunkCode * chunk = parser.GetChunk();

	this->EnterFunctionScope( true );

	try
	{
		EpilChunkInterpreter ci( this );
		ci.Run( chunk );
	}
	catch( ... )
	{
		this->LeaveScope();
		EpilPointerHelper::SafeDelete( chunk );
		throw;
	}

	this->LeaveScope();

	EpilPointerHelper::SafeDelete( chunk );
}


void EpilInterpreter::CreateGlobalVariable( const tstring & name )
{
	this->CreateGlobalVariable( name, this->CreateNilObject() );
}


void EpilInterpreter::CreateGlobalVariable( const tstring & name, EpilObject * value )
{
	m_globalScope->SetLocal( name, value );
}


void EpilInterpreter::RegisterFunction( const tstring & name, EpilNativeFunction function )
{
	if( function == NULL )
	{
		throw EpilInterpreterException( t("C++ function to register cannot be null.") );
	}
	EpilNativeFunctionObject * obj = this->CreateNativeFunctionObject( function );
	this->SetGlobalVariable( name, obj );
}


EpilObject * EpilInterpreter::GetGlobalVariable( const tstring & name ) const
{
	return m_globalScope->GetLocal( name );
}


void EpilInterpreter::SetGlobalVariable( const tstring & name, EpilObject * value )
{
	m_globalScope->SetLocal( name, value );
}


EpilObject * EpilInterpreter::GetVariable( const tstring & name ) const
{
	EpilScope * scope = m_curScope;
	EpilObject * result = NULL;
	do
	{
		result = scope->GetLocal( name );
		scope = scope->GetParent();
	} while( result->GetType() == EOT_Nil && scope != NULL );
	return result;
}


void EpilInterpreter::SetVariable( const tstring & name, EpilObject * value )
{
	EpilScope * scope = m_curScope;

	while( scope )
	{
		EpilObject * result = scope->GetLocal( name );
		if( result->GetType() != EOT_Nil )
		{
			result->ReleaseCopy();
			scope->SetLocal( name, value );
			return;
		}
		scope = scope->GetParent();
	}

	this->SetGlobalVariable( name, value );
}


void EpilInterpreter::EnterScope()
{
	EpilScope * scope = new EpilScope( this, m_curScope );
	m_curScope = scope;
}


void EpilInterpreter::EnterFunctionScope( bool hasVariousParameters )
{
	EpilFunctionScope * scope = new EpilFunctionScope( this, m_curScope, hasVariousParameters );
	m_curScope = scope;
}


void EpilInterpreter::LeaveScope()
{
	EpilScope * parent = m_curScope->GetParent();
	EpilPointerHelper::SafeDelete( m_curScope );
	m_curScope = parent;
}


bool EpilInterpreter::HasVariousParameters() const
{
	EpilScope * scope = m_curScope;

	while( scope )
	{
		if( scope->GetScopeType() == EST_Function )
		{
			EpilFunctionScope * functionScope = static_cast<EpilFunctionScope*>( scope );
			return functionScope->HasVariousParameters();
		}
		scope = scope->GetParent();
	}

	throw EpilInterpreterException( t("Error function scope detected.") );
}


const EpilObjectVector & EpilInterpreter::GetVariousParameters() const
{
	EpilScope * scope = m_curScope;

	while( scope )
	{
		if( scope->GetScopeType() == EST_Function )
		{
			EpilFunctionScope * functionScope = static_cast<EpilFunctionScope*>( scope );
			return functionScope->GetVariousParameters();
		}
		scope = scope->GetParent();
	}

	throw EpilInterpreterException( t("Error function scope detected.") );
}


void EpilInterpreter::SetVariousParameters( const EpilObjectVector & variousParameters )
{
	EpilScope * scope = m_curScope;

	while( scope )
	{
		if( scope->GetScopeType() == EST_Function )
		{
			EpilFunctionScope * functionScope = static_cast<EpilFunctionScope*>( scope );
			functionScope->SetVariousParameters( variousParameters );
			return;
		}
		scope = scope->GetParent();
	}

	throw EpilInterpreterException( t("Error function scope detected.") );
}


const EpilObjectVector & EpilInterpreter::GetReturns() const
{
	EpilScope * scope = m_curScope;

	while( scope )
	{
		if( scope->GetScopeType() == EST_Function )
		{
			EpilFunctionScope * functionScope = static_cast<EpilFunctionScope*>( scope );
			return functionScope->GetReturns();
		}
		scope = scope->GetParent();
	}

	throw EpilInterpreterException( t("Error function scope detected.") );
}


void EpilInterpreter::SetReturns( const EpilObjectVector & returns )
{
	EpilScope * scope = m_curScope;

	while( scope )
	{
		if( scope->GetScopeType() == EST_Function )
		{
			EpilFunctionScope * functionScope = static_cast<EpilFunctionScope*>( scope );
			functionScope->SetReturns( returns );
			return;
		}
		scope = scope->GetParent();
	}

	throw EpilInterpreterException( t("Error function scope detected.") );
}